Add ARIA roles and sort attributes to ProcessTable#18
Add ARIA roles and sort attributes to ProcessTable#18jbingham17 wants to merge 1 commit intomainfrom
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR adds comprehensive ARIA accessibility attributes and semantic roles to the ProcessTable component, including role declarations for tables, rows, and cells, along with aria-sort and aria-selected attributes. Additionally, package.json is updated with changes to dependencies or configurations. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/ProcessTable.tsx (1)
99-167:⚠️ Potential issue | 🟠 MajorAdd
role="cell"to data cells for valid ARIA table structure.
role="row"must own cell-type elements (cell,columnheader,rowheader, orgridcellper WAI-ARIA 1.2). Data cells on lines 155+ are plainspanelements without explicit roles, breaking table semantics for screen readers. Addrole="cell"to each data cell span.Proposed fix
<span className="col-pid">{process.pid}</span> - <span className="col-user">{process.user.substring(0, 8)}</span> - <span className="col-cpu" style={{ color: getCpuColor(process.cpu) }}> + <span role="cell" className="col-user">{process.user.substring(0, 8)}</span> + <span role="cell" className="col-cpu" style={{ color: getCpuColor(process.cpu) }}> {process.cpu.toFixed(1).padStart(5, ' ')} </span> - <span className="col-mem" style={{ color: getMemColor(process.mem) }}> + <span role="cell" className="col-mem" style={{ color: getMemColor(process.mem) }}> {process.mem.toFixed(1).padStart(5, ' ')} </span> - <span className="col-virt">{process.vsz}</span> - <span className="col-res">{process.rss}</span> - <span className="col-state">{process.stat.charAt(0)}</span> - <span className="col-time">{process.time}</span> - <span className="col-command">{process.command}</span> + <span role="cell" className="col-virt">{process.vsz}</span> + <span role="cell" className="col-res">{process.rss}</span> + <span role="cell" className="col-state">{process.stat.charAt(0)}</span> + <span role="cell" className="col-time">{process.time}</span> + <span role="cell" className="col-command">{process.command}</span>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ProcessTable.tsx` around lines 99 - 167, The data cell spans inside the rendered rows (the map over filteredAndSortedProcesses in ProcessTable.tsx) lack ARIA cell roles; update each span that renders process fields (the spans for pid, user, col-cpu, col-mem, col-virt, col-res, col-state, col-time, col-command) to include role="cell" so the row (role="row") contains proper cell elements for screen readers, preserving existing props like className, style, onClick behavior and values produced by getCpuColor/getMemColor, selectedPid, and process.field usage.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/components/ProcessTable.tsx
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ProcessTable.tsx`:
- Around line 101-142: The sortable column header spans (those with class
"sortable" for PID, USER, CPU%, MEM%, and COMMAND) are not keyboard-accessible;
update each span (e.g., the spans calling handleSort('pid'), handleSort('user'),
handleSort('cpu'), handleSort('mem'), handleSort('command')) to be focusable by
adding tabIndex={0} and add an onKeyDown handler that listens for Enter and
Space and invokes the same handleSort(...) call; ensure existing aria-sort logic
using sortField and sortDirection remains unchanged so screen readers still get
state.
---
Outside diff comments:
In `@src/components/ProcessTable.tsx`:
- Around line 99-167: The data cell spans inside the rendered rows (the map over
filteredAndSortedProcesses in ProcessTable.tsx) lack ARIA cell roles; update
each span that renders process fields (the spans for pid, user, col-cpu,
col-mem, col-virt, col-res, col-state, col-time, col-command) to include
role="cell" so the row (role="row") contains proper cell elements for screen
readers, preserving existing props like className, style, onClick behavior and
values produced by getCpuColor/getMemColor, selectedPid, and process.field
usage.
| <span | ||
| role="columnheader" | ||
| className="col-pid sortable" | ||
| onClick={() => handleSort('pid')} | ||
| aria-sort={sortField === 'pid' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | ||
| > | ||
| PID{getSortIndicator('pid')} | ||
| </span> | ||
| <span | ||
| role="columnheader" | ||
| className="col-user sortable" | ||
| onClick={() => handleSort('user')} | ||
| aria-sort={sortField === 'user' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | ||
| > | ||
| USER{getSortIndicator('user')} | ||
| </span> | ||
| <span | ||
| role="columnheader" | ||
| className="col-cpu sortable" | ||
| onClick={() => handleSort('cpu')} | ||
| aria-sort={sortField === 'cpu' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | ||
| > | ||
| CPU%{getSortIndicator('cpu')} | ||
| </span> | ||
| <span | ||
| role="columnheader" | ||
| className="col-mem sortable" | ||
| onClick={() => handleSort('mem')} | ||
| aria-sort={sortField === 'mem' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | ||
| > | ||
| MEM%{getSortIndicator('mem')} | ||
| </span> | ||
| <span className="col-virt">VIRT</span> | ||
| <span className="col-res">RES</span> | ||
| <span className="col-state">S</span> | ||
| <span className="col-time">TIME</span> | ||
| <span role="columnheader" className="col-virt">VIRT</span> | ||
| <span role="columnheader" className="col-res">RES</span> | ||
| <span role="columnheader" className="col-state">S</span> | ||
| <span role="columnheader" className="col-time">TIME</span> | ||
| <span | ||
| role="columnheader" | ||
| className="col-command sortable" | ||
| onClick={() => handleSort('command')} | ||
| aria-sort={sortField === 'command' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | ||
| > |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/components/ProcessTable.tsx | head -200 | tail -120Repository: coderabbit-demo/btop
Length of output: 5370
🌐 Web query:
ARIA sortable column header keyboard accessibility tabindex enter space
💡 Result:
- Put the sorting control in the header as a real
<button>(e.g., wrap the header text in a button inside<th>). Then you get keyboard focus via Tab and activation with Enter/Space for free—notabindexneeded on the<th>. (w3.org) - Use
aria-sorton the<th>of the currently sorted column only, togglingascending/descendingas sort changes, and move/removearia-sortwhen a different column becomes sorted. (w3.org) - Don’t rely on
role="columnheader"for keyboard behavior:columnheaderhas no special keyboard interaction; the button is what must be operable. (developer.mozilla.org)
Minimal pattern:
<th scope="col" aria-sort="ascending">
<button type="button">Company</button>
</th>
<th scope="col">
<button type="button">Last name</button>
</th>If you can’t use a real <button>, make the control focusable and clickable:
tabindex="0",role="button", and handle Enter + Space (typicallykeydownfor Space withpreventDefault()so the page doesn’t scroll).
Citations:
- 1: https://www.w3.org/WAI/ARIA/apg/patterns/table/examples/sortable-table/?utm_source=openai
- 2: https://www.w3.org/WAI/ARIA/apg/patterns/table/examples/sortable-table/?utm_source=openai
- 3: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/columnheader_role?utm_source=openai
Sortable headers lack keyboard accessibility.
The sortable column headers (pid, user, cpu, mem, command at lines 101–142) have onClick handlers but are not focusable and cannot be activated via keyboard. Add tabIndex={0} and onKeyDown handlers for Enter/Space to all five sortable headers, or replace with <button> elements inside the columnheader spans for built-in keyboard support.
Proposed fix (apply to all sortable headers)
<span
role="columnheader"
className="col-pid sortable"
onClick={() => handleSort('pid')}
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ handleSort('pid');
+ }
+ }}
aria-sort={sortField === 'pid' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
PID{getSortIndicator('pid')}
</span>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span | |
| role="columnheader" | |
| className="col-pid sortable" | |
| onClick={() => handleSort('pid')} | |
| aria-sort={sortField === 'pid' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| PID{getSortIndicator('pid')} | |
| </span> | |
| <span | |
| role="columnheader" | |
| className="col-user sortable" | |
| onClick={() => handleSort('user')} | |
| aria-sort={sortField === 'user' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| USER{getSortIndicator('user')} | |
| </span> | |
| <span | |
| role="columnheader" | |
| className="col-cpu sortable" | |
| onClick={() => handleSort('cpu')} | |
| aria-sort={sortField === 'cpu' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| CPU%{getSortIndicator('cpu')} | |
| </span> | |
| <span | |
| role="columnheader" | |
| className="col-mem sortable" | |
| onClick={() => handleSort('mem')} | |
| aria-sort={sortField === 'mem' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| MEM%{getSortIndicator('mem')} | |
| </span> | |
| <span className="col-virt">VIRT</span> | |
| <span className="col-res">RES</span> | |
| <span className="col-state">S</span> | |
| <span className="col-time">TIME</span> | |
| <span role="columnheader" className="col-virt">VIRT</span> | |
| <span role="columnheader" className="col-res">RES</span> | |
| <span role="columnheader" className="col-state">S</span> | |
| <span role="columnheader" className="col-time">TIME</span> | |
| <span | |
| role="columnheader" | |
| className="col-command sortable" | |
| onClick={() => handleSort('command')} | |
| aria-sort={sortField === 'command' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| <span | |
| role="columnheader" | |
| className="col-pid sortable" | |
| onClick={() => handleSort('pid')} | |
| tabIndex={0} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| handleSort('pid'); | |
| } | |
| }} | |
| aria-sort={sortField === 'pid' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| PID{getSortIndicator('pid')} | |
| </span> | |
| <span | |
| role="columnheader" | |
| className="col-user sortable" | |
| onClick={() => handleSort('user')} | |
| aria-sort={sortField === 'user' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| USER{getSortIndicator('user')} | |
| </span> | |
| <span | |
| role="columnheader" | |
| className="col-cpu sortable" | |
| onClick={() => handleSort('cpu')} | |
| aria-sort={sortField === 'cpu' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| CPU%{getSortIndicator('cpu')} | |
| </span> | |
| <span | |
| role="columnheader" | |
| className="col-mem sortable" | |
| onClick={() => handleSort('mem')} | |
| aria-sort={sortField === 'mem' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > | |
| MEM%{getSortIndicator('mem')} | |
| </span> | |
| <span role="columnheader" className="col-virt">VIRT</span> | |
| <span role="columnheader" className="col-res">RES</span> | |
| <span role="columnheader" className="col-state">S</span> | |
| <span role="columnheader" className="col-time">TIME</span> | |
| <span | |
| role="columnheader" | |
| className="col-command sortable" | |
| onClick={() => handleSort('command')} | |
| aria-sort={sortField === 'command' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} | |
| > |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ProcessTable.tsx` around lines 101 - 142, The sortable column
header spans (those with class "sortable" for PID, USER, CPU%, MEM%, and
COMMAND) are not keyboard-accessible; update each span (e.g., the spans calling
handleSort('pid'), handleSort('user'), handleSort('cpu'), handleSort('mem'),
handleSort('command')) to be focusable by adding tabIndex={0} and add an
onKeyDown handler that listens for Enter and Space and invokes the same
handleSort(...) call; ensure existing aria-sort logic using sortField and
sortDirection remains unchanged so screen readers still get state.
Summary
role="table"andaria-labelto the process table containerrole="columnheader"witharia-sortto sortable column headersrole="row"andaria-selectedto process rowsTest plan
aria-selected🤖 Generated with Claude Code
Summary by CodeRabbit